package com.ming_za.odupdaterserver.core.utils.account;

import com.ming_za.odupdaterserver.core.utils.account.api.AccountStorage;
import com.ming_za.odupdaterserver.core.utils.account.api.LevelValidator;
import com.ming_za.odupdaterserver.core.utils.account.api.PasswordOperator;
import com.ming_za.odupdaterserver.core.utils.account.exception.*;
import com.ming_za.odupdaterserver.core.utils.account.param.*;
import com.ming_za.odupdaterserver.core.utils.account.preparatory.AccountType;
import com.ming_za.odupdaterserver.core.utils.account.token.TokenPackage;
import com.ming_za.odupdaterserver.core.utils.account.param.ReturnAccountPackage;

import java.util.List;

/**
 * 账号管理器，主要负责账号相关的功能，所有功能都通过该类调用
 */
public class AccountManager {

    /**
     * 是否需要账号，只有一个管理用户，不需要考虑多用户时可关闭账号，登录时只需密码即可
     */
    private final boolean hasAccount;

    /**
     * 创建账号时的默认等级
     */
    private final int defaultLevel;

    /**
     * 注册时记录ip
     */
    private final boolean recordIpWhenRegister;

    /**
     * 登录时检测ip，当功能关闭时，存储的lastIp为null
     * <p>当开启,但存储的lastIp为null时，也会报登录异常，可通过{@link #setAccountIp}进行设置ip再登录</p>
     */
    private final boolean checkIpWhenLogin;

    /**
     * token验证时检查ip
     */
    private final boolean checkIpWhenCheckToken;

    /**
     * token操作对象
     */
    private final TokenManager tokenManager;

    /**
     * 密码操作对象，用于加密处理
     */
    private final PasswordOperator passwordOperator;

    /**
     * 账号存储操作对象
     */
    private final AccountStorage accountStorage;

    /**
     * 等级检验器
     */
    private final LevelValidator levelValidator;

    private AccountManager(boolean hasAccount, int defaultLevel, boolean recordIpWhenRegister, boolean checkIpWhenLogin, boolean CheckIpWhenCheckToken, TokenManager tokenManager, PasswordOperator passwordOperator, AccountStorage accountStorage, LevelValidator levelValidator) {
        this.hasAccount = hasAccount;
        this.defaultLevel = defaultLevel;
        this.recordIpWhenRegister = recordIpWhenRegister;
        this.checkIpWhenLogin = checkIpWhenLogin;
        this.checkIpWhenCheckToken = CheckIpWhenCheckToken;
        this.tokenManager = tokenManager;
        this.passwordOperator = passwordOperator;
        this.accountStorage = accountStorage;
        this.levelValidator = levelValidator;
    }

    /**
     * 创建账户管理器
     * @param accountType 账户类型，详情见{@link AccountType}
     * @param tokenManager token管理器，使用{@link TokenManager#createTokenManager}创建
     * @param passwordOperator 密码操作接口
     * @param accountStorage 账号存储接口
     * @param levelValidator 等级检验器，传入已有的{@link LevelValidator}实现类或者自行编写实现类
     */
    public static AccountManager createAccountManager(AccountType accountType,TokenManager tokenManager,PasswordOperator passwordOperator,AccountStorage accountStorage,LevelValidator levelValidator){
        return new AccountManager(
                accountType.isHasAccount(),
                accountType.getDefaultLevel(),
                accountType.isRecordIpWhenRegister(),
                accountType.isCheckIpWhenLogin(),
                accountType.isCheckIpWhenCheckToken(),
                tokenManager,passwordOperator,accountStorage,levelValidator);
    }

    /**
     * 注册账号
     * 仅有一个账号时可调用该方法，创建账号为0
     * 在此状态是账户等级几乎没有意义，如果需要用请调用{@link #registerAccount(long, String, String, int)}
     * @param password 密码
     * @param ip ip，可为null
     */
    public ReturnAccountPackage<Void> registerAccount(String password, String ip){
        return registerAccount(0,password,ip,defaultLevel);
    }

    public ReturnAccountPackage<Void> registerAccount(long account, String password, String ip) {
        return registerAccount(account,password,ip,defaultLevel);
    }

    /**
     * 注册账号
     * <pre>
     *     如果您需要在注册ip时对lastIp进行操作，可以用{@link #accountStorage}进行修改覆盖
     *     例如：当注册记录ip为true，可以在存储时将注册ip写到lastIp上
     * </pre>
     * @param account 账号
     * @param password 密码（未加密）
     * @param ip 注册时ip，可为null
     * @param level 等级，可选
     */
    public ReturnAccountPackage<Void> registerAccount(long account, String password, String ip, int level) {

        if (!hasAccount && !accountStorage.getAllAccountPackage().isEmpty()){
//            throw new AccountExistException("账号已存在");
            return new ReturnAccountPackage<>(true,AccountRE.ACCOUNT_REGISTERED,null);
        }

        AccountPackage accountPackage = accountStorage.getAccountPackage(account);
        if (accountPackage != null) {
//            throw new AccountExistException("账号已存在");
            return new ReturnAccountPackage<>(true,AccountRE.ACCOUNT_REGISTERED,null);
        }

        //通过账号存储接口进行存储
        accountStorage.saveAccount(new AccountPackage(
                account,
                passwordOperator.encrypt(password),
                null,
                null,
                null,
                System.currentTimeMillis(),
                recordIpWhenRegister ? ip : null,
                level
        ));

        return new ReturnAccountPackage<>(true,AccountRE.SUCCESS,null);
    }

    /**
     * 登录，单账户且不使用ip时可调用该方法
     * @param password 密码（未加密）
     * @return 登录成功返回token
     */
    public ReturnAccountPackage<String> loginAccount(String password) throws AccountException {
        return loginAccount(password,null);
    }

    /**
     * 使用单账号且有ip时可调用该方法
     * @param password 密码（未加密）
     * @param ip ip
     * @return 登录成功返回token
     */
    public ReturnAccountPackage<String> loginAccount(String password, String ip) throws AccountException {
        return loginAccount(0,password,ip);
    }

    /**
     * 使用多账户且不使用ip时可调用该方法
     * @param account 账号
     * @param password 密码
     * @return 登录成功返回token
     */
    public ReturnAccountPackage<String> loginAccount(long account, String password) throws AccountException {
        return loginAccount(account,password,null);
    }

    /**
     * 获取所有账户
     * @return 账户列表
     */
    public List<AccountPackage> getAllAccount(){
        return accountStorage.getAllAccountPackage();
    }

    /**
     * 登录
     * @param account 账号
     * @param password 密码（未加密）
     * @param ip ip
     * @return 登录成功返回token
     */
    public ReturnAccountPackage<String> loginAccount(long account, String password, String ip) throws AccountException {

        AccountPackage accountPackage = accountStorage.getAccountPackage(account);
        if (accountPackage == null){
//            throw new AccountNotFoundException("找不到账号");
            return new ReturnAccountPackage<>(false,AccountRE.ACCOUNT_UNREGISTERED,null);
        }

        if (checkIpWhenLogin && !ip.equals(accountPackage.getLastIp())){
//            throw new AccountIpInconsistencyException("ip不符");
            return new ReturnAccountPackage<>(false,AccountRE.LOGIN_IP_NOT_ALLOW,null);
        }

        if (!passwordOperator.encrypt(password).equals(accountPackage.getPassword())){
//            throw new AccountPasswordWrongException("密码错误");
            return new ReturnAccountPackage<>(false,AccountRE.WRONG_ACCOUNT_OR_PASSWORD,null);
        }

        TokenPackage token = tokenManager.createToken(account);

        accountStorage.saveLogin(account,token.getKey(),ip,System.currentTimeMillis());
//        return token.getToken();
        return new ReturnAccountPackage<>(true,AccountRE.SUCCESS,token.getToken());
    }

    /**
     * 检查token，单账户调用
     * @param token token
     * @param ip ip，可为null
     * @return 当开启token续签时，验证成功将返回新的token
     */
    public ReturnAccountPackage<String> checkToken(String token, String ip){
        return checkToken(0,token,ip,0);
    }

    /**
     * 检查token
     * @param account 账号
     * @param token token
     * @param ip ip，可为null
     * @param level 等级
     * @return 当开启token续签时，验证成功将返回新的token
     */
    public ReturnAccountPackage<String> checkToken(long account, String token, String ip, int level){

        AccountPackage accountPackage = accountStorage.getAccountPackage(account);

        if (accountPackage == null){
//            throw new AccountNotFoundException("找不到账号");
            return new ReturnAccountPackage<>(false,AccountRE.ACCOUNT_UNREGISTERED,null);
        }

        if (checkIpWhenCheckToken && accountPackage.getLastIp() != null && !ip.equals(accountPackage.getLastIp())){
//            throw new TokenFailureException("token验证失败");
            return new ReturnAccountPackage<>(false,AccountRE.TOKEN_FAILURE,null);
        }

        if (!levelValidator.valid(accountPackage.getLevel(),level)){
//            throw new AccountNoPermissionException("没有权限执行该操作！");
            return new ReturnAccountPackage<>(false,AccountRE.NO_PERMISSION,null);
        }

        ReturnAccountPackage<TokenPackage> tokenPackageReturnAccountPackage = tokenManager.checkToken(account, token, accountPackage.getTokenKey());

        if (tokenPackageReturnAccountPackage.getData() != null){
            //当续签开启时，把续签的token保存到数据库中
            accountStorage.setToken(account, tokenPackageReturnAccountPackage.getData().getKey());
//            return tokenResult.getToken();
            return new ReturnAccountPackage<>(true,AccountRE.SUCCESS, tokenPackageReturnAccountPackage.getData().getToken());
        }
        return new ReturnAccountPackage<>(true,AccountRE.SUCCESS,null);
    }

    /**
     * 修改密码，用于单账户
     * @param oldPassword 旧密码
     * @param newPassword 新密码
     */
    public ReturnAccountPackage<Void> modifyPassword(String oldPassword, String newPassword){
        return modifyPassword(0,oldPassword,newPassword);
    }

    /**
     * 修改密码
     * @param account 用户
     * @param oldPassword 旧密码
     * @param newPassword 新密码
     */
    public ReturnAccountPackage<Void> modifyPassword(long account, String oldPassword, String newPassword){
        AccountPackage accountPackage = accountStorage.getAccountPackage(account);

        if (!passwordOperator.encrypt(oldPassword).equals(accountPackage.getPassword())){
            return new ReturnAccountPackage<>(false,AccountRE.WRONG_ACCOUNT_OR_PASSWORD,null);

        }

        forceModifyPassword(account,newPassword);
        return new ReturnAccountPackage<>(true,AccountRE.SUCCESS,null);
    }

    /**
     * 强制修改密码，用于单账户
     * @param password 密码
     */
    public void forceModifyPassword(String password){
        forceModifyPassword(0,password);
    }

    /**
     * 强制修改密码
     * @param account 账号
     * @param password 密码
     */
    public void forceModifyPassword(long account,String password){
        accountStorage.setPassword(account,passwordOperator.encrypt(password));
        accountStorage.setToken(account,null);
    }

    /**
     * 强制设置账号ip
     * @param account 账号
     * @param ip ip
     */
    public void setAccountIp(long account, String ip) {
        accountStorage.setLastIp(account,ip);
    }
}
